//################################################################################
// MIT License
// Copyright (c) 2024 ZhangYihua
//
// Change Logs:
// Date           Author       Notes
// 2020-12-20     ZhangYihua   first version
//
// Description  : 
//################################################################################

module cos_wave #(
parameter           WAVE_DW                 = 12,
parameter           WAVE_AM                 = 2047,     // WAVE_AM < 2^(WAVE_DW-1)
parameter           NCO_DW                  = 16,
parameter           PHS_DW                  = 11        // 2< PHS_DW <=NCO_DW
) ( 
input                                       rst_n,
input                                       clk,
input                                       cke,

input               [NCO_DW-1:0]            freq_word,  // output frequency is F_clk*freq_word/(2^NCO_DW)

output  reg signed  [WAVE_DW-1:0]           cos_dat
);

//################################################################################
// define local varialbe and localparam
//################################################################################
localparam          TAB_DW                  = $clog2(WAVE_AM+1);
localparam          TAB_AW                  = PHS_DW-2;
localparam          TAB_NUM                 = 1<<TAB_AW;
localparam          PI                      = 3.1415926536;
localparam [TAB_DW-1:0]     TAB_MAX         = WAVE_AM;
localparam [WAVE_DW-1:0]    WAVE_MAX        = WAVE_AM;

wire                [PHS_DW-1:0]            phase;
wire                [TAB_AW-1:0]            phase_low;
wire                [TAB_AW-1:0]            phase_mir;
reg                 [TAB_AW-1:0]            rd_addr;
reg                                         rd_cinv;
reg                 [TAB_DW-1:0]            rd_cos_1d;
reg                                         rd_cinv_1d;
wire        signed  [WAVE_DW-1:0]           rd_pcos_1d;
wire        signed  [WAVE_DW-1:0]           rd_ncos_1d;
wire                [TAB_NUM*TAB_DW-1:0]    cos_ary;
reg                 [TAB_DW-1:0]            cos_rom[TAB_NUM-1:0];

//################################################################################
// main
//################################################################################

nco #(
        .PHS_FW                         (PHS_DW                         ),
        .NCO_FW                         (NCO_DW                         ),
        .FREQ_DW                        (NCO_DW                         ),	// FREQ_DW<=NCO_FW or FREQ_DW>NCO_FW is both OK
        .RND_EN                         (1'b1                           )	// 1'b0:truncate lower bits directly; 1'b1:rounding, but over_1p is not used in this mode
) u_nco ( 
        .rst_n                          (rst_n                          ),
        .clk                            (clk                            ),
        .cke                            (cke                            ),

        .freq_word                      (freq_word                      ),	// output frequency is F_clk*freq_word/(2^ACC_DW)

        .over_1p                        (                               ),	// combinational logic output
        .phase                          (phase                          )   // it's maybe early than acc_over if RDN_EN=1'b1
);

assign phase_low = phase[0+:TAB_AW];
assign phase_mir = {TAB_AW{1'b1}} - phase_low;

always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        rd_addr <=`U_DLY {TAB_AW{1'b0}};
        rd_cinv <=`U_DLY 1'b0;
    end else if (cke==1'b1) begin
        case(phase[PHS_DW-1-:2])
            2'b00   : {rd_addr, rd_cinv} <=`U_DLY {phase_low, 1'b0};
            2'b01   : {rd_addr, rd_cinv} <=`U_DLY {phase_mir, 1'b1};
            2'b10   : {rd_addr, rd_cinv} <=`U_DLY {phase_low, 1'b1};
            default : {rd_addr, rd_cinv} <=`U_DLY {phase_mir, 1'b0};
        endcase
    end else
        ;
end

always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        rd_cos_1d <=`U_DLY TAB_MAX;
    end else if (cke==1'b1) begin
        rd_cos_1d <=`U_DLY cos_rom[rd_addr];
    end else
        ;
end

always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        rd_cinv_1d <=`U_DLY 1'b0;
    end else if (cke==1'b1) begin
        rd_cinv_1d <=`U_DLY rd_cinv;
    end else
        ;
end

assign rd_pcos_1d = {{WAVE_DW-TAB_DW{1'b0}}, rd_cos_1d};
assign rd_ncos_1d = $signed({WAVE_DW{1'b0}}) - rd_pcos_1d;

always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        cos_dat <=`U_DLY WAVE_MAX;
    end else if (cke==1'b1) begin
        if (rd_cinv_1d==1'b0)
            cos_dat <=`U_DLY rd_pcos_1d;
        else
            cos_dat <=`U_DLY rd_ncos_1d;
    end else
        ;
end

assign cos_ary = tab_f(WAVE_AM);

always@(*) begin:ROM
    integer i;

    for(i=0; i<TAB_NUM; i=i+1) begin
        cos_rom[i] = cos_ary[i*TAB_DW+:TAB_DW];
    end
end

function [TAB_NUM*TAB_DW-1:0] tab_f;
    input integer am;

    reg [TAB_DW:0]  val;
    integer         i;
    real            ph;
    begin
        for (i=0; i<TAB_NUM; i=i+1) begin
            ph = (PI/2)*(i+0.5)/TAB_NUM;

            val = $cos(ph)*(am*2.0);

            tab_f[i*TAB_DW+:TAB_DW] = val[1+:TAB_DW] + val[0];
        end
    end
endfunction

//################################################################################
// ASSERTION
//################################################################################

`ifdef CBB_ASSERT_ON
// synopsys translate_off


// synopsys translate_on
`endif

endmodule
